-------------------------------------------------------------------------------
-- Title      : Asynchronous fifo
-- Project    : 
-------------------------------------------------------------------------------
-- File       : afifo.vhd
-- Author     : Paul W
-- Company    : 
-- Created    : 2012-10-29
-- Last update: 2012-12-11
-- Platform   : 
-- Standard   : VHDL'93
-------------------------------------------------------------------------------
-- Description: Asynchronous fifo with flags for full, almost full, empty and
--              almost empty.
--              
--              The 'almost' flags are activated if the levels are set other
--              than 0. The levels indicate when to flag 'almost' based on
--              words left. E.g Almost empty flag at 5 will signal whenever
--              there are 5 or fewer words left, and almost full at 5 will flag
--              whenever there are 5 or fewer open write spots (i.e. the fifo
--              has at least 251 words stored).
-------------------------------------------------------------------------------
-- Copyright (c) 2012 
-------------------------------------------------------------------------------
-- Revisions  :
-- Date        Version  Author  Description
-- 2012-10-29  1.0      paul    Created
-------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;

package afifo_CMP is
  component afifo is
    generic (
      NUM_WORDS    : positive;
      WORD_WIDTH   : positive;
      AL_EMPTY_LEV : natural;
      AL_FULL_LEV  : natural);
    port (
      in_clkWr   : in  std_logic;
      in_clkRd   : in  std_logic;
      in_rst     : in  std_logic;
      in_we      : in  std_logic;
      in_re      : in  std_logic;
      in_din     : in  std_logic_vector(WORD_WIDTH-1 downto 0);
      out_aEmpty : out std_logic;
      out_empty  : out std_logic;
      out_aFull  : out std_logic;
      out_full   : out std_logic;
      out_dout   : out std_logic_vector(WORD_WIDTH-1 downto 0));
  end component afifo;
end package afifo_CMP;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.functions.all;

entity afifo is
  
  generic (
    NUM_WORDS    : positive := 256;
    WORD_WIDTH   : positive := 16;
    AL_EMPTY_LEV : natural  := 0;
    AL_FULL_LEV  : natural  := 0);

  port (
    in_clkWr   : in  std_logic;
    in_clkRd   : in  std_logic;
    in_rst     : in  std_logic;
    in_we      : in  std_logic;
    in_re      : in  std_logic;
    in_din     : in  std_logic_vector(WORD_WIDTH-1 downto 0);
    out_aEmpty : out std_logic;
    out_empty  : out std_logic;
    out_aFull  : out std_logic;
    out_full   : out std_logic;
    out_dout   : out std_logic_vector(WORD_WIDTH-1 downto 0)
    );
end entity afifo;

architecture rtl of afifo is

  constant WIDTH : integer := log2_ceil(NUM_WORDS);

  type mem_t is array (natural range <>) of std_logic_vector(WORD_WIDTH-1 downto 0);

  signal fifoMem           : mem_t(NUM_WORDS-1 downto 0);
  signal rdPtr             : unsigned(WIDTH-1 downto 0);
  signal wrPtr             : unsigned(WIDTH-1 downto 0);
  signal wrPtrSyncd        : unsigned(WIDTH-1 downto 0);
  signal rdPtrWrapped      : std_logic;
  signal wrPtrWrapped      : std_logic;
  signal wrPtrWrappedSyncd : std_logic;
  signal ptrReady          : std_logic;
  signal ptrAck            : std_logic;
  signal syncDin           : std_logic_vector(1+(WIDTH-1) downto 0);
  signal syncDout          : std_logic_vector(1+(WIDTH-1) downto 0);
  signal runningCnt        : natural range 0 to NUM_WORDS;  -- Count of words in the
                                        -- fifo. We prevent this
                                        -- from rolling.
  
  
begin  -- architecture rtl

  -- Process for reads
  -- Do not read if fifo is empty
  read_proc : process (in_clkRd) is
  begin  -- process
    if rising_edge(in_clkRd) then       -- rising clock edge
      if in_rst = '1' then
        rdPtr        <= (others => '0');
        rdPtrWrapped <= '0';
      elsif (in_re = '1') and (runningCnt > 0) then
        out_dout <= fifoMem(to_integer(rdPtr));
        if rdPtr = NUM_WORDS-1 then
          rdPtr        <= (others => '0');
          rdPtrWrapped <= not rdPtrWrapped;
        else
          rdPtr <= rdPtr + 1;
        end if;
      end if;
    end if;
  end process;

  -- Process for writes
  -- Stop writing if the fifo is full
  write_proc : process (in_clkWr) is
  begin  -- process write_proc
    if rising_edge(in_clkWr) then       -- rising clock edge
      if in_rst = '1' then
        wrPtr        <= (others => '0');
        wrPtrWrapped <= '0';
      elsif (in_we = '1') and (runningCnt < NUM_WORDS) then
        fifoMem(to_integer(wrPtr)) <= in_din;
        if wrPtr = NUM_WORDS-1 then
          wrPtr        <= (others => '0');
          wrPtrWrapped <= not wrPtrWrapped;
        else
          wrPtr <= wrPtr + 1;
        end if;
      end if;
    end if;
  end process write_proc;

  syncDin <= wrPtrWrapped & std_logic_vector(wrPtr);
  
  -- Sync the wrPtr to the read clock domain
  rdGetWrPtr_U : entity work.crossDomainSync
    generic map (
      WIDTH => WIDTH+1)
    port map (
      in_rst    => in_rst,
      in_srcClk => in_clkWr,
      in_data   => syncDin,
      in_ack    => ptrAck,
      out_data  => syncDout,
      out_ready => ptrReady
      );

  wrPtrWrappedSyncd <= syncDout(WIDTH);
  wrPtrSyncd        <= unsigned(syncDout(WIDTH-1 downto 0));
  
  -- We will use the above crossDomainSync to bring the write pointer into the
  -- read clock domain. This gives an inherent pessimism to the determinations
  -- of full/empty because not every change is guaranteed to be sampled, plus
  -- there is added delay with crossing domains.
  syncPointers_proc : process (in_clkRd, in_rst) is
    variable numRdSide : natural;
  begin  -- process syncPointers_proc
    if in_rst = '1' then                -- asynchronous reset (active high)
      ptrAck     <= '0';
      runningCnt <= 0;
    elsif rising_edge(in_clkRd) then
      if ptrReady = '1' then            -- update runningCnt
        ptrAck <= '1';                  -- Acknowledge we have gotten the ptr
        if rdPtrWrapped = wrPtrWrappedSyncd then
          -- If both have wrapped, the disparity is a simple subtraction
          runningCnt <= to_integer(wrPtrSyncd - rdPtr);
        else
          -- Can presume the writer has wrapped, but the reader has not
          numRdSide  := NUM_WORDS - to_integer(rdPtr);
          runningCnt <= numRdSide + to_integer(wrPtrSyncd);
        end if;
      else
        -- Only stop the ack when ptrReady goes low!
        ptrAck <= '0';
      end if;
    end if;
  end process syncPointers_proc;

  -- Generate flags asynchronously, else they are a tick slow
  flag_proc : process (runningCnt) is
  begin  -- process flag_proc
    out_full   <= '0';
    out_empty  <= '0';
    out_aFull  <= '0';
    out_aEmpty <= '0';

    -- Trigger the flag signals
    if (NUM_WORDS-runningCnt) <= AL_FULL_LEV then
      out_aFull <= '1';
    end if;
    if runningCnt <= AL_EMPTY_LEV then
      out_aEmpty <= '1';
    end if;
    if runningCnt = NUM_WORDS then
      out_full  <= '1';
      out_aFull <= '0';
    end if;
    if runningCnt = 0 then
      out_empty  <= '1';
      out_aEmpty <= '0';
    end if;
  end process flag_proc;
  
end architecture rtl;
